/*
 * Unidata Platform
 * Copyright (c) 2013-2020, UNIDATA LLC, All rights reserved.
 *
 * Commercial License
 * This version of Unidata Platform is licensed commercially and is the appropriate option for the vast majority of use cases.
 *
 * Please see the Unidata Licensing page at: https://unidata-platform.com/license/
 * For clarification or additional options, please contact: info@unidata-platform.com
 * -------
 * Disclaimer:
 * -------
 * THIS SOFTWARE IS DISTRIBUTED "AS-IS" WITHOUT ANY WARRANTIES, CONDITIONS AND
 * REPRESENTATIONS WHETHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE
 * IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, MERCHANTABLE QUALITY,
 * FITNESS FOR A PARTICULAR PURPOSE, DURABILITY, NON-INFRINGEMENT, PERFORMANCE AND
 * THOSE ARISING BY STATUTE OR FROM CUSTOM OR USAGE OF TRADE OR COURSE OF DEALING.
 */
/**
 *
 */
package org.unidata.mdm.rest.data.converter;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.unidata.mdm.core.type.data.Attribute;
import org.unidata.mdm.core.type.data.BinaryLargeValue;
import org.unidata.mdm.core.type.data.CharacterLargeValue;
import org.unidata.mdm.core.type.data.MeasuredValue;
import org.unidata.mdm.core.type.data.extended.WinnerInformationSimpleAttribute;
import org.unidata.mdm.core.type.data.impl.BinaryLargeValueImpl;
import org.unidata.mdm.core.type.data.impl.BlobSimpleAttributeImpl;
import org.unidata.mdm.core.type.data.impl.BooleanSimpleAttributeImpl;
import org.unidata.mdm.core.type.data.impl.CharacterLargeValueImpl;
import org.unidata.mdm.core.type.data.impl.ClobSimpleAttributeImpl;
import org.unidata.mdm.core.type.data.impl.DateSimpleAttributeImpl;
import org.unidata.mdm.core.type.data.impl.EnumSimpleAttributeImpl;
import org.unidata.mdm.core.type.data.impl.IntegerSimpleAttributeImpl;
import org.unidata.mdm.core.type.data.impl.MeasuredSimpleAttributeImpl;
import org.unidata.mdm.core.type.data.impl.NumberSimpleAttributeImpl;
import org.unidata.mdm.core.type.data.impl.StringSimpleAttributeImpl;
import org.unidata.mdm.core.type.data.impl.TimeSimpleAttributeImpl;
import org.unidata.mdm.core.type.data.impl.TimestampSimpleAttributeImpl;
import org.unidata.mdm.core.type.lob.LargeObjectAcceptance;
import org.unidata.mdm.core.type.model.AttributeElement;
import org.unidata.mdm.data.type.data.EtalonRecord;
import org.unidata.mdm.data.type.keys.RecordOriginKey;
import org.unidata.mdm.rest.data.ro.ExtendedSimpleAttributeRO;
import org.unidata.mdm.rest.data.ro.LargeObjectRO;
import org.unidata.mdm.rest.data.ro.SimpleAttributeRO;
import org.unidata.mdm.rest.system.ro.SimpleDataType;
import org.unidata.mdm.search.dto.SearchResultHitDTO;
import org.unidata.mdm.search.dto.SearchResultHitFieldDTO;

/**
 * @author Mikhail Mikhailov
 */
public class SimpleAttributeConverter {


    /**
     * Constructor.
     */
    private SimpleAttributeConverter() {
        super();
    }

    /**
     * Convert Simple data attribute to REST simple data attribute.
     *
     * @param source the source
     */
    public static org.unidata.mdm.core.type.data.SimpleAttribute<?> from(SimpleAttributeRO source) {

        if (source == null) {
            return null;
        }

        org.unidata.mdm.core.type.data.SimpleAttribute target = null;
        if (source.getType() != null) {

            switch (source.getType()) {
                case BOOLEAN:
                    target = new BooleanSimpleAttributeImpl(source.getName(), (Boolean) source.getValue());
                    break;
                case DATE:
                    target = new DateSimpleAttributeImpl(source.getName(),
                            Objects.isNull(source.getValue())
                                    ? null
                                    : (LocalDate) source.getValue());
                    break;
                case TIME:
                    target = new TimeSimpleAttributeImpl(source.getName(),
                            Objects.isNull(source.getValue())
                                    ? null
                                    : (LocalTime) source.getValue());
                    break;
                case TIMESTAMP:
                    target = new TimestampSimpleAttributeImpl(source.getName(),
                            Objects.isNull(source.getValue())
                                    ? null
                                    : (LocalDateTime) source.getValue());
                    break;
                case INTEGER:
                    target = new IntegerSimpleAttributeImpl(source.getName(), (Long) source.getValue());
                    break;
                case NUMBER:
                    if (StringUtils.isBlank(source.getUnitId()) && StringUtils.isBlank(source.getValueId())) {
                        target = new NumberSimpleAttributeImpl(source.getName(), (Double) source.getValue());
                    } else {
                        target = new MeasuredSimpleAttributeImpl(source.getName(), source.getValueId(), source.getUnitId(), (Double) source.getValue());
                    }
                    break;
                case STRING:
                    target = new StringSimpleAttributeImpl(source.getName(), (String) source.getValue());
                    break;
                case BLOB:
                    // Bytes aren't transfered via normal save/get calls
                    LargeObjectRO blob = (LargeObjectRO) source.getValue();
                    target = new BlobSimpleAttributeImpl(source.getName(),
                            blob == null ? null : new BinaryLargeValueImpl()
                                    .withFileName(blob.getFileName())
                                    .withId(blob.getId())
                                    .withMimeType(blob.getMimeType())
                                    .withSize(blob.getSize())
                                    .withAcceptance(blob.getAcceptance() == null ? LargeObjectAcceptance.PENDING : LargeObjectAcceptance.valueOf(blob.getAcceptance())));
                    break;
                case CLOB:
                    // Content is not transfered via normal save/get calls
                    LargeObjectRO clob = (LargeObjectRO) source.getValue();
                    target = new ClobSimpleAttributeImpl(source.getName(),
                            clob == null ? null : new CharacterLargeValueImpl()
                                    .withFileName(clob.getFileName())
                                    .withId(clob.getId())
                                    .withMimeType(clob.getMimeType())
                                    .withSize(clob.getSize())
                                    .withAcceptance(clob.getAcceptance() == null ? LargeObjectAcceptance.PENDING : LargeObjectAcceptance.valueOf(clob.getAcceptance())));
                    break;
                default:
                    break;
            }
        }

        return target;
    }

    /**
     * Copy list of simple attributes.
     *
     * @param source source list
     * @return collection
     */
    public static Collection<Attribute> from(List<SimpleAttributeRO> source) {

        if (CollectionUtils.isEmpty(source)) {
            return Collections.emptyList();
        }

        List<Attribute> destination = new ArrayList<>(source.size());
        for (SimpleAttributeRO a : source) {
            destination.add(from(a));
        }

        return destination;
    }

    /**
     * Copy list of simple attributes.
     *
     * @param source source list
     * @param target target
     */
    public static void to(Collection<org.unidata.mdm.core.type.data.SimpleAttribute<?>> source, Collection<SimpleAttributeRO> target) {
        if (source == null || source.isEmpty()) {
            return;
        }

        for (org.unidata.mdm.core.type.data.SimpleAttribute a : source) {
            SimpleAttributeRO attributeRO = to(a);
            if (attributeRO != null) {
                target.add(attributeRO);
            }
        }
    }

    /**
     * Copy list of simple attributes with additional information.
     *
     * @param source source list
     * @param target target list
     * @param originKey origin key
     * @param etalonRecord etalon record for source
     */
    public static void to(Collection<org.unidata.mdm.core.type.data.SimpleAttribute<?>> source, Collection<SimpleAttributeRO> target, EtalonRecord etalonRecord, RecordOriginKey originKey) {
        if (CollectionUtils.isEmpty(source)) {
            return;
        }

        for (org.unidata.mdm.core.type.data.SimpleAttribute<?> sourceAttribute : source) {
            if (sourceAttribute != null) {
                ExtendedSimpleAttributeRO targetAttribute = new ExtendedSimpleAttributeRO();
                populate(sourceAttribute, targetAttribute);
                org.unidata.mdm.core.type.data.SimpleAttribute<?> winnerAttribute = etalonRecord.getSimpleAttribute(sourceAttribute.getName());

                targetAttribute.setWinner(winnerAttribute instanceof WinnerInformationSimpleAttribute
                        && originKey.getExternalId().equals(((WinnerInformationSimpleAttribute) winnerAttribute).getWinnerExternalId())
                        && originKey.getSourceSystem().equals(((WinnerInformationSimpleAttribute) winnerAttribute).getWinnerSourceSystem()));

                target.add(targetAttribute);
            }

        }
    }

    /**
     * Convert simple attributes from one search hit to collection RO attributes
     *
     * @param hit source hit
     * @param prefix prefix for extract data from hit
     * @param attributesDef attributes definition
     * @return collection RO attributes
     */
    public static List<SimpleAttributeRO> to(SearchResultHitDTO hit, String prefix, Collection<AttributeElement> attributesDef) {
        List<SimpleAttributeRO> result = null;
        if (CollectionUtils.isNotEmpty(attributesDef)) {
            result = new ArrayList<>();
            for (AttributeElement attributeDef : attributesDef) {
                SearchResultHitFieldDTO hf = hit.getFieldValue(prefix + attributeDef.getName());
                if (hf != null && hf.isNonNullField()) {
                    SimpleAttributeRO simpleAttributeRO = new SimpleAttributeRO();
                    simpleAttributeRO.setName(attributeDef.getName());
                    simpleAttributeRO.setType(SimpleDataType.fromValue(defineDataType(attributeDef)));
                    Object value = hf.getFirstValue();
                    if (value != null) {
                        switch (simpleAttributeRO.getType()) {
                            case DATE:
                                value = LocalDate.parse(value.toString());
                                break;
                            case TIMESTAMP:
                                value = LocalDateTime.parse(value.toString());
                                break;
                            case TIME:
                                value = LocalTime.parse(value.toString());
                                break;
                        }

                        simpleAttributeRO.setDisplayValue(hf.getFirstDisplayValue());
                        simpleAttributeRO.setTargetEtalonId(hf.getSystemId());
                    }
                    simpleAttributeRO.setValue(value);
                    result.add(simpleAttributeRO);
                }
            }
        }
        return result;
    }

    /**
     * Convert Simple data attribute to REST simple data attribute.
     *
     * @param source the source
     */
    public static SimpleAttributeRO to(org.unidata.mdm.core.type.data.SimpleAttribute<?> source) {
        if (source == null) {
            return null;
        }

        SimpleAttributeRO target = new SimpleAttributeRO();
        populate(source, target);
        return target;
    }

    protected static void populate(org.unidata.mdm.core.type.data.SimpleAttribute<?> source, SimpleAttributeRO target) {

        target.setName(source.getName());
        if (source.getDataType() != null) {
            switch (source.getDataType()) {
                case BOOLEAN:
                    target.setValue(source.getValue());
                    break;
                case DATE:
                    target.setValue(source.getValue() == null
                            ? null
                            : (LocalDate) source.castValue());
                    break;
                case TIME:
                    target.setValue(source.getValue() == null
                            ? null
                            : (LocalTime) source.castValue());
                    break;
                case TIMESTAMP:
                    target.setValue(source.getValue() == null
                            ? null
                            : (LocalDateTime) source.castValue());
                    break;
                case INTEGER:
                    target.setValue(source.getValue());
                    target.setDisplayValue(((IntegerSimpleAttributeImpl) source).getDisplayValue());
                    target.setTargetEtalonId(((IntegerSimpleAttributeImpl) source).getLinkEtalonId());
                    break;
                case NUMBER:
                    target.setValue(source.getValue());
                    break;
                case MEASURED:
                    MeasuredSimpleAttributeImpl measuredSimpleAttribute = (MeasuredSimpleAttributeImpl) source;
                    MeasuredValue v = measuredSimpleAttribute.getValue();
                    if (v == null || v.getCategoryId() == null) {
                        return;
                    }
                    target.setValue(v.getInitialValue());
                    target.setUnitId(v.getUnitId());
                    target.setValueId(v.getCategoryId());
                    break;
                case STRING:
                    target.setValue(source.getValue());
                    target.setDisplayValue(((StringSimpleAttributeImpl) source).getDisplayValue());
                    target.setTargetEtalonId(((StringSimpleAttributeImpl) source).getLinkEtalonId());
                    break;
                case ENUM:
                    target.setValue(((EnumSimpleAttributeImpl) source).getValue());
                    target.setDisplayValue(((EnumSimpleAttributeImpl) source).getDisplayValue());
                    break;
                case BLOB:
                    BinaryLargeValue sourceBlob = source.castValue();
                    if (sourceBlob != null) {

                        LargeObjectRO targetBlob = new LargeObjectRO();
                        targetBlob.setId(sourceBlob.getId());
                        targetBlob.setFileName(sourceBlob.getFileName());
                        targetBlob.setMimeType(sourceBlob.getMimeType());
                        targetBlob.setSize(sourceBlob.getSize());
                        targetBlob.setAcceptance(sourceBlob.getAcceptance().name());

                        target.setValue(targetBlob);
                    }
                    break;
                case CLOB:
                    CharacterLargeValue sourceClob = source.castValue();
                    if (sourceClob != null) {

                        LargeObjectRO targetClob = new LargeObjectRO();
                        targetClob.setId(sourceClob.getId());
                        targetClob.setFileName(sourceClob.getFileName());
                        targetClob.setMimeType(sourceClob.getMimeType());
                        targetClob.setSize(sourceClob.getSize());

                        target.setValue(targetClob);
                    }
                    break;
                default:
                    break;
            }
            if (source.getDataType() == org.unidata.mdm.core.type.data.SimpleAttribute.SimpleDataType.MEASURED) {
                target.setType(SimpleDataType.NUMBER);
            } else if (source.getDataType() == org.unidata.mdm.core.type.data.SimpleAttribute.SimpleDataType.ENUM) {
                target.setType(SimpleDataType.STRING);
            } else {
                target.setType(SimpleDataType.fromValue(source.getDataType().name()));
            }
        }
    }

    private static String defineDataType(AttributeElement simpleAttributeDef) {
        return simpleAttributeDef.getValueType().name();
    }
}
